package org.yunai.swjg.server.core.service;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.yunai.swjg.server.core.annotation.MainThread;
import org.yunai.swjg.server.core.session.GameSession;
import org.yunai.swjg.server.entity.User;
import org.yunai.swjg.server.module.player.PlayerExitReason;
import org.yunai.swjg.server.module.player.persistance.PlayerDataUpdater;
import org.yunai.swjg.server.module.player.vo.Player;
import org.yunai.swjg.server.module.scene.core.AbstractScene;
import org.yunai.swjg.server.module.scene.core.SceneDef;
import org.yunai.yfserver.common.HeartBeatable;
import org.yunai.yfserver.common.LoggerFactory;
import org.yunai.yfserver.message.IMessage;
import org.yunai.yfserver.message.MessageQueue;
import org.yunai.yfserver.util.Assert;

import java.util.concurrent.atomic.AtomicLong;

/**
 * 在线信息<br />
 * <pre>
 *     1. 当用户登录后，{@link Online#user}将被赋值，此时可以成为[用户在线信息]
 *     2. 当用户加载后角色后，{@link Online#player}将被复制，此时可以成为[玩家在线信息]
 * </pre>
 * User: yunai
 * Date: 13-3-29
 * Time: 下午4:19
 */
public class Online implements HeartBeatable {

    private static final Logger LOGGER_MSG = LoggerFactory.getLogger(LoggerFactory.Logger.msg, Online.class);
    private static final Logger LOGGER_PLAYER = LoggerFactory.getLogger(LoggerFactory.Logger.player, Online.class);
    private static final Logger LOGGER_UPDATER = LoggerFactory.getLogger(LoggerFactory.Logger.updater, Online.class);

    /**
     * 处理的消息总数<br />
     * 为避免同步，在主线程中修改
     */
    private static final AtomicLong playerMessageCount = new AtomicLong(0);
    /**
     * 处理的出错消息总数<br />
     */
    private static final AtomicLong playerErrorMessageCount = new AtomicLong(0);

    /**
     * 帐号信息
     */
    private User user;
    /**
     * 角色信息
     */
    private Player player;
    /**
     * 服务器编号
     */
    private Short serverId;
    /**
     * 游戏会话
     */
    private GameSession session;
    /**
     * 在线状态管理
     */
    private OnlineStateService stateService;
    /**
     * 玩家退出服务器原因
     */
    private PlayerExitReason exitReason;
    /**
     * 玩家所在场景
     */
    private AbstractScene scene;
    /**
     * 玩家数据更新调度器
     */
    private PlayerDataUpdater dataUpdater;
    /**
     * 玩家消息队列
     */
    private MessageQueue msgQueue;

    public Online(GameSession session) {
        if (session == null) {
            throw new IllegalArgumentException("session can not be null!");
        }
        session.setOnline(this);
        this.session = session;
        this.stateService = new OnlineStateService();
        if (session.isConnected()) {
            this.setState(OnlineState.connected);
        }
    }

    public Player getPlayer() {
        return player;
    }

    public void setPlayer(Player player) {
        this.player = player;
    }

    public void write(IMessage msg) {
        session.write(msg);
    }

    public GameSession getSession() {
        return session;
    }

    public void setSession(GameSession session) {
        this.session = session;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Short getServerId() {
        return serverId;
    }

    public void setServerId(Short serverId) {
        this.serverId = serverId;
    }

    public OnlineState getState() {
        return this.stateService.getState();
    }

    public PlayerExitReason getExitReason() {
        return exitReason;
    }

    public void setExitReason(PlayerExitReason exitReason) {
        this.exitReason = exitReason;
    }

    public PlayerDataUpdater getDataUpdater() {
        return dataUpdater;
    }

    public AbstractScene getScene() {
        return scene;
    }

    /**
     * @return IP
     */
    public String getIp() {
        return session.getIp();
    }

    // ==================== 逻辑部分 ====================

    /**
     * 初始化<br />
     * 该方法在玩家所有信息加载前调用
     */
    public void init() {
        this.dataUpdater = new PlayerDataUpdater();
        this.msgQueue = new MessageQueue();
        // TODO dataNotifier
    }

    /**
     * 设置玩家所在场景<br />
     * <b>该方法必须在主线程中调用</b>
     *
     * @param scene 场景
     */
    @MainThread
    public void setScene(AbstractScene scene) {
        // TODO check thread
        this.scene = scene;
    }

    /**
     * 设置{@link OnlineState#gaming}里的子状态
     *
     * @param gamingState 子状态
     */
    public synchronized void setGamingState(GamingState gamingState) {
        this.stateService.setGamingState(gamingState);
    }

    /**
     * @return 获得{@link OnlineState.gaming}里的子状态
     */
    public GamingState getGamingState() {
        return this.stateService.getGamingState();
    }

    /**
     * 设置在线状态
     *
     * @param state 目标状态
     */
    public void setState(OnlineState state) {
        // TODO check thread
        OnlineState curState = this.getState();
        if (curState == state) {
            return;
        }
        if (!curState.canChange(state)) {
            LOGGER_PLAYER.error("[setState] [{} -> {} is not allow].", curState, state);
        }
        this.stateService.setState(state);
    }

    /**
     * @return 是否连接
     */
    public boolean isConnected() {
        return this.session != null && this.session.isConnected();
    }

    /**
     * 断开连接
     */
    public void disconnect() {
        if (isConnected()) {
            session.close();
        }
    }

    /**
     * @return 在线玩家是否在场景中
     */
    public boolean isInScene() {
        return this.scene != null;
    }

    /**
     * @return 是否在普通场景中
     */
    public boolean isInNormalScene() {
        return isInScene() && scene.getType() == SceneDef.Type.NORMAL;
    }

    /**
     * @return 是否在副本场景中
     */
    public boolean isInRepScene() {
        return isInScene() && (scene.getType() == SceneDef.Type.REP);
    }

    /**
     * @return 是否在活动副本场景中
     */
    public boolean isInActivityRepScene() {
        return isInScene() && (scene.getType() == SceneDef.Type.ACTIVITY);
    }

    /**
     * 添加消息到场景消息队列
     *
     * @param msg 消息
     */
    public void putMessage(IMessage msg) {
        if (msg == null) {
            return;
        }
        this.msgQueue.put(msg);
        playerMessageCount.incrementAndGet();
    }

    /**
     * 验证消息有效性
     *
     * @param msg 消息
     * @return 是否有效
     */
    public boolean validateMessage(IMessage msg) {
        return this.stateService.validateMessage(msg);
    }

    /**
     * 处理服务器收到来自玩家的消息<br />
     * 在玩家当前所属的场景线程中调用
     */
    public void processMessage() {
        for (int i = 0; i < 12; i++) {
            // 获得消息并验证
            if (this.msgQueue.isEmpty()) {
                break;
            }
            IMessage msg = this.msgQueue.get();
            Assert.notNull(msg);
            if (!this.validateMessage(msg)) {
                LOGGER_MSG.error("[processMessage] [online.state({}) can't put message({})].", this.getState(), msg.getCode());
                continue;
            }

            // 执行消息
            long beginTime = System.nanoTime();
            try {
                msg.execute();
            } catch (Throwable e) {
                playerErrorMessageCount.incrementAndGet();
                LOGGER_PLAYER.error("[processMessage] [error:{}].", ExceptionUtils.getStackTrace(e));
// TODO 发送错误消息给客户端
                this.exitReason = PlayerExitReason.SERVER_ERROR;
                this.disconnect();
            } finally {
                long costTime = (System.nanoTime() - beginTime) / 1000000;
                if (costTime > 0) {
                    LOGGER_MSG.info("[process] [code:{}, time: {}ms] [total: {}, error: {}]",
                            msg.getCode(), costTime, playerMessageCount.get(), playerErrorMessageCount.get());
                }
            }
        }
    }

    @Override
    public void heartBeat() {
        // TODO 属性递推下去heartBeat
        this.updateData();
    }

    /**
     * 更新数据
     */
    public void updateData() {
        try {
            this.dataUpdater.process();
        } catch (Exception e) {
            LOGGER_UPDATER.error("[updateData] [error:{}]", ExceptionUtils.getStackTrace(e));
        }
    }

    /**
     * 加载离线玩家Online<br />
     * 1. [角色基本信息]<br />
     * 2. [背包信息]<br />
     * 该Online处于{@link OnlineState#unconnected}
     *
     * @param playerId 玩家编号
     * @return 在线信息
     */
    // TODO 需要调用下计算人物属性方法，否则数据会是错的
    public static Online loadOffline(Integer playerId) {
        Online online = new Online(new GameSession(null));
//        // 角色基本信息
//        PlayerEntity playerEntity = playerMapper.selectPlayer(playerId);
//        online.setPlayer(Player.build(online, playerEntity));
//        Player player = online.getPlayer();
//        online.init();
//        // 背包信息
//        Inventory inventory = inventoryService.loadInventory(player);
//        online.setInventory(inventory);
        return online;
    }
}
